package oojo.data.impl;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import javax.persistence.Id;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Utility class for for bean properties.
 */
public class Properties {

    public static final Pattern getterPattern = Pattern.compile("(get|is)([A-Z][a-zA-Z_0-9]*)+");
    public static final Pattern setterPattern = Pattern.compile("set([A-Z][a-zA-Z_0-9]*)+");

    private static final Logger logger = LoggerFactory.getLogger(Properties.class);
    private static final Map<Class<?>, String> idProperties = new HashMap<Class<?>, String>();
    private static final Map<Class<?>, Map<String, PropertyDescriptor>> descriptors = new HashMap<Class<?>, Map<String, PropertyDescriptor>>();
    
    public static Object id(Class<?> klass, Object obj) throws Exception {
        String idProperty = idProperty(klass);
        Method idPropertyGetter = descriptor(klass, idProperty).getReadMethod();
        return idPropertyGetter.invoke(obj);
    }
    
    public static String idProperty(Class<?> klass) {
        if (!idProperties.containsKey(klass)) {
            synchronized(idProperties) {
                if (!idProperties.containsKey(klass)) {
                    idProperties.put(klass, findIdProperty(klass, false));
                }
            }
        }
        
        return idProperties.get(klass);
    }
    
    private static String findIdProperty(Class<?> klass, boolean prob) {
        logger.info("Searching id property name in class: " + klass.getName());
        
        for (Field field : klass.getDeclaredFields()) {
            String fieldName = field.getName();
            
            if (field.isAnnotationPresent(Id.class)) {
                logger.info("Found id annotation on field: " + fieldName);
                return fieldName;
            } else if (fieldName.equals("id")) {
                prob = true;
            }
        }
        
        for (Method method : klass.getDeclaredMethods()) {
            String methodName = method.getName();
            
            if (method.isAnnotationPresent(Id.class)) {
                if (methodName.startsWith("get") || methodName.startsWith("set")) {
                    logger.info("Found id annotation on method: " + methodName);
                    return Introspector.decapitalize(methodName.substring(3));
                } else {
                    throw new RuntimeException("Found id annotation on method: " + methodName);
                }
            } else if (methodName.equals("getId") || methodName.equals("setId")) {
                prob = true;
            }
        }
        
        Class<?> superKlass = klass.getSuperclass();
        
        if (superKlass.equals(Object.class)) {
            if (prob) {
                return "id";
            }
            
            throw new RuntimeException("Reached top of class hierarchy but didn't find id property name");
        }
        
        return findIdProperty(superKlass, prob);
    }
    
    public static PropertyDescriptor descriptor(Class<?> klass, String propertyName) throws Exception {
        if (!descriptors.containsKey(klass)) {
            synchronized(descriptors) {
                if (!descriptors.containsKey(klass)) {
                    descriptors.put(klass, new HashMap<String, PropertyDescriptor>());
                }
            }
        }
        
        Map<String, PropertyDescriptor> classDescriptors = descriptors.get(klass);
        
        if (!classDescriptors.containsKey(propertyName)) {
            synchronized(classDescriptors) {
                if (!classDescriptors.containsKey(propertyName)) {
                    classDescriptors.put(propertyName, new PropertyDescriptor(propertyName, klass));
                }
            }
        }
        
        return classDescriptors.get(propertyName);
    }
    
    public static Method getter(Class<?> klass, String propertyName) {
        Pattern pattern = Pattern.compile("(get|is)" + capitalize(propertyName));
        Method getter = null;
        
        for (Method method : klass.getMethods()) {
            if (pattern.matcher(method.getName()).matches() && method.getParameterTypes().length == 0) {
                getter = method;
                break;
            }
        }
        
        return getter;
    }
    
    public static Method setter(Class<?> klass, String propertyName) {
        Pattern pattern = Pattern.compile("set" + capitalize(propertyName));
        Method setter = null;
        
        for (Method method : klass.getMethods()) {
            if (pattern.matcher(method.getName()).matches() && method.getParameterTypes().length == 1) {
                setter = method;
                break;
            }
        }
        
        return setter;
    }

    public static String capitalize(String name) {
        return Character.toUpperCase(name.charAt(0)) + name.substring(1); 
    }
}
